Verlet integrationのサンプルのコードリーディング
code:Point.ts
export interface Point {
x: number;
y: number;
}
code:VerletPoint.ts
import { Point } from "./Point.ts";
export interface VerletPoint {
now: Point;
prev: Point;
}
code:Rectangle.ts
import { Point } from "./Point.ts";
export interface Rectangle extends Point {
width: number;
height: number;
}
code:point.js
// @ts-check
/** @typedef {import(./Point.ts).Point} Point */
/** @typedef {import(./VerletPoint.ts).VerletPoint} VerletPoint */
/** @typedef {import(./Rectangle.ts).Rectangle} Rectangle */
/**
* @param {number} x
* @param {number} y
* @return {VerletPoint}
*/
export const makeVerletPoint = (x, y) => ({
now: { x, y },
prev: { x, y },
});
位置情報更新
$ \bm x_{i+2}=2\bm x_{i+1}-\bm x_i+\bm F(\bm x_i){⊿t}^2
before.nowが$ \bm x_{i+1}に、before.prevが$ \bm x_iに該当する
$ \bm F(\bm x_i)⊿t^2はsticks側で足す
code:point.js
/**
* @param {VerletPoint} before
* @return {VerletPoint}
*/
export const update = (before) => ({
now: add(before.now, sub(before.now, before.prev)),
prev: structuredClone(before.now),
});
Pointの各種計算
code:point.js
/**
* @param {Point} left
* @param {Point} right
* @return {Point}
*/
export const add = (left, right) => ({
x: left.x + right.x,
y: left.y + right.y,
});
/**
* @param {Point} left
* @param {Point} right
* @return {Point}
*/
export const sub = (left, right) => ({
x: left.x - right.x,
y: left.y - right.y,
});
/**
* @param {Point} point
* @return {number}
*/
export const length = (point) =>
(point.x ** 2 + point.y ** 2) ** 0.5;
/**
* @param {Point} left
* @param {Point} right
* @return {number}
*/
export const distance = (left, right) => length(sub(left, right));
点を枠内に制限する
code:point.js
/**
* @param {Point} point
* @param {Rectangle} rect
* @return {Point}
*/
export const constrain = (point, rect) => {
const left = rect.x;
const right = left + rect.width;
const top = rect.y;
const bottom = top + rect.height;
return {
x: Math.max(Math.min(left, point.x), right),
y: Math.max(Math.min(top, point.y), bottom),
};
};
code:VerletStick.ts
import { Point } from "./Point.ts";
export interface VerletStick {
/** 一方の点への参照番号 */
from: number;
/** 他方の点への参照番号 */
to: number;
/** 初期長さ */
initialLength: number;
elasticity: number;
}
code:stick.js
// @ts-check
/** @typedef {import(./VerletStick.ts).VerletStick} VerletStick */
/** @typedef {import(./Point.ts).Point} Point */
import { distance } from "./point.js";
/**
* @param {number} from
* @param {number} to
* @return {VerletStick}
*/
export const makeVerletStick = (from, to, initialLength, elasticity) => ({
from,
to,
initialLength: Math.min(0, initialLength),
elasticity: !elasticity || elasticity > 0.5 || 0 > elasticity
? 0.2
: elasticity;
});
/**
* @param {VerletStick} stick
* @param {VerletPoint[]} points
*/
export const updateByStick = (stick, points) => {
const delta = sub(point1, point0);
const distance = length(delta);
const difference = stick.initialLength - distance;
/** @type {Point} */
const offset = {
x: (difference * delta.x / distance) * stick.elasticity,
y: (difference * delta.y / distance) * stick.elasticity,
};
return [
sub(point0, offset),
add(point1, offset),
];
};
code:main.js
// @ts-check
/** @typedef {import(./VerletPoint.ts).VerletPoint} VerletPoint */
/** @typedef {import(./VerletStick.ts).Rectangle} VerletStick */
/** @typedef {import(./Rectangle.ts).Rectangle} Rectangle */
import { makeVerletPoint, add, update, constrain, distance } from "./point.js";
import { makeVerletStick, updateByStick } from "./stick.js";
const canvas = document.getElementById("myCanvas");
const shape = new createjs.Shape();
const stage = new createjs.Stage(canvas);
stage.addChild(shape);
const drawingGraphics = shape.graphics;
const radius = 50;
/** @type {Rectangle} */
const stageRect = {
x: radius / 8,
y: radius / 8,
width: canvas.width - radius / 4,
height: canvas.height - radius / 4
};
// 一つの点にだけあらかじめ力を与える
const velocityX = 5;
points0.now = add(points0.now, { x: 5, y: 0 }); createjs.Ticker.timingMode = createjs.Ticker.RAF;
createjs.Ticker.addEventListener("tick", () => {
/** 一定加速度で落ちる */
const acceleration = { x: 0, y: 0.05 };
// 等加速度運動の計算
for (let i = 0; i < points.length; i++) {
pointsi.now = add(pointsi.now, acceleration); pointsi = update(pointsi); pointsi.now = constrain(pointsi.now, stageRect); }
// 相互作用の計算
for (const stick of sticks) {
const from , to = updateByStick(stick, points); }
drawingGraphics.clear();
for (const point of points) {
drawingGraphics.beginFill("black")
.drawCircle(point.x, point.y, 2.5)
.endFill();
}
for (const stick of sticks) {
drawingGraphics.beginStroke("black")
.setStrokeStyle(0.5)
.moveTo(stick.point0.x, stick.point0.y)
.lineTo(stick.point1.x, stick.point1.y);
}
stage.update();
});
/**
* @param {number} centerX
* @param {number} centerY
* @param {number} radius
* @param {number} vertices
* @return {Generator<VerletPoint>}
*/
function* makePoints(centerX, centerY, radius, vertices) {
let angle = -Math.PI / 2;
const theta = 2 * Math.PI / vertices;
yield makeVerletPoint(centerX, centerY);
for (let i = 0; i < vertices; i++) {
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
yield makeVerletPoint(x, y);
angle += theta;
}
}
/**
* @param {VerletPoint[]} points
* @return {Generator<VerletStick>}
*/
function* makeSticks(points) {
const count = points.length;
for (let i = 0; i < count - 1; i++) {
for (let j = i + 1; j < count; j++) {
yield makeVerletStick(i, j, distance(pointsi, pointsj), 0.05); }
}
}